﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using Microsoft.Xna.Framework.Graphics;
using Microsoft.Xna.Framework;
using Microsoft.Xna.Framework.Content;

namespace FloralTerrainGenerator.Source {
	class Terrain {

		#region field variables
		Random r = new Random();

		RenderTarget2D alternateRenderTarget;
		PresentationParameters pp = FloralTerrainGenerator.gd.PresentationParameters;

        //lighting stuff
        float ambientLight = 0.2f;
        Vector3 lightPos = new Vector3(0, -500, 0);
        float lightPower = 1.2f;

		public VertexMultiTex[] vertices;
		private int[] triVerts; 
		VertexBuffer vBuff;
		VertexBuffer waterBuff;

        VertexElement[] vE = new VertexElement[] {
			new VertexElement(0, 0, VertexElementFormat.Vector3, VertexElementMethod.Default, VertexElementUsage.Position, 0),
			new VertexElement(0, sizeof(float)*3, VertexElementFormat.Vector2, VertexElementMethod.Default, VertexElementUsage.TextureCoordinate, 0),
			new VertexElement(0, sizeof(float)*5, VertexElementFormat.Vector3, VertexElementMethod.Default, VertexElementUsage.Normal, 0),
			new VertexElement(0, sizeof(float)*8, VertexElementFormat.Vector4, VertexElementMethod.Default, VertexElementUsage.TextureCoordinate, 1)
		};

		//a list of the vertices at each node of the terrain
		List<int>[] pointIndex;

		Cell[] cellArray;

		List<Water> waterList = new List<Water>();

		float width, depth;
		float lowest, heighest;
		int cellsWide, cellsDeep, totalCells;
		float cellWidth, cellDepth;
		int totalFaces;
		int totalNumVertices;
		int totalNumPoints, numPointsX, numPointsZ;
		float[,] pointHeights;

		float terrainStartX, terrainStartZ;


		public Effect effect;
		Boolean wf = false;


		//texturing stuff
		Texture2D texZero;
        Texture2D texOne;
		Texture2D texTwo;
		Texture2D texThree;

		public int texSpread = 8;


		//procedural infos
		public Biome biome0;
		public Biome biome1;
		public Biome biome2;
		public Biome biome3;

		int floralDensity;
		Vector3 randomVector = Vector3.Zero;
		public List<Plant> floraMan = new List<Plant>();

		//Conway stuff
		Boolean[,] conwayGrid; //positions in the conwayGrid will be converted to world coordinates later
		//instead of killing or birthing cells as we iterate the list we will put them in a list and kill or birth them all at the end
		//to avoid anomalies caused by order of evaluation
		List<Vector2> cellsToDie; //cell that will be killed identified by conwayGrid coordinates
		List<Vector2> cellsToBirth; //cell that will be birthed identified by conwayGrid coordinates

		#endregion

		#region constructor
		/// <summary>
		/// 
		/// </summary>
		/// <param name="pWidth">Width</param>
		/// <param name="pDepth">Depth</param>
		/// <param name="pVertsWide">How many vertices width-wise</param>
		/// <param name="pVertsDeep">How many vertices depth-wise</param>
		/// <param name="pLowest">How low should complete black be</param>
		/// <param name="pHeighest">How high should complete white be</param>
		/// <param name="heightmap">Location of the heightmap</param>
		public Terrain(int pWidth, int pDepth, int pVertsWide, int pVertsDeep, int pLowest, int pHeighest, float b0, float b1, float b2, float b3, String heightmap) {

			biome0 = new Biome(b0);
			biome1 = new Biome(b1);
			biome2 = new Biome(b2);
			biome3 = new Biome(b3);

			width = pWidth;
			depth = pDepth;
			cellsWide = pVertsWide-1;
			cellsDeep = pVertsDeep-1;
			lowest = pLowest;
			heighest = pHeighest;

			cellArray = new Cell[cellsWide*cellsDeep];

			effect = FloralTerrainGenerator.sContent.Load<Effect>("TerrainShader");

			effect.Parameters["World"].SetValue(Matrix.Identity);
			effect.Parameters["View"].SetValue(FloralTerrainGenerator.viewMatrix);
			float ar = GraphicsDeviceManager.DefaultBackBufferWidth/GraphicsDeviceManager.DefaultBackBufferHeight;
			effect.Parameters["Projection"].SetValue(Matrix.CreatePerspectiveFieldOfView(MathHelper.PiOver4, ar, 1, 2000));

			createGrid(cellsWide, cellsDeep);

			pointHeights = new float[numPointsZ, numPointsX];

			//heightmap stuff
			Texture2D hM = FloralTerrainGenerator.sContent.Load<Texture2D>(heightmap);
			float[,] heights = new float[hM.Width, hM.Height];

			heights = getColorData(hM);

			//compress color data
			for(int a=0;a<hM.Height;a++) {
				for(int b=0;b<hM.Width;b++) {
					float height = heights[a, b];
					height = height/255;
					height *= (heighest-lowest);
					height += lowest;
					heights[a, b] = height;
				}
			}

			//assign heights
			int stepper = 0;
			int zStep = hM.Height/(numPointsZ-1);
			int xStep = hM.Width/(numPointsX-1);
			int xPos, zPos;

			for(int a=0; a<numPointsZ; a++) {
				for(int b=0; b<numPointsX; b++) {
					xPos = xStep*b;
					zPos = zStep*a;
					if(xPos == heights.GetLength(1)) {
						xPos -= 1;
					}

					if(zPos == heights.GetLength(0)) {
						zPos -= 1;
					}

					//positionPointY(stepper, heights[zPos, xPos]);
					vertices[stepper].Position.Y = heights[zPos,xPos];
					setTextureWeight(stepper,heights[zPos,xPos]);
					pointHeights[a, b] = heights[zPos,xPos];
					stepper++;
					if (stepper >= numPointsZ * numPointsX) {
						a = hM.Height + 1;
						b = hM.Width + 1;
					}
				}
			}

			//assign normals
			setNormals();

			//average normals of all vertices in a point
			//for(int a=0; a<pointIndex.Length; a++) {
			//    Vector3 norm = Vector3.Zero;
			//    foreach(int b in pointIndex[a]) {
			//        norm += vertices[b].Normal;
			//    }

			//    norm /= pointIndex[a].Count;
			//    norm.Normalize();

			//    foreach(int b in pointIndex[a]) {
			//        vertices[b].Normal = norm;
			//    }
			//}

			//figure out heights of cells
			//float sum = 0;
			//for(int a = 0; a<cellArray.Length; a++) {
			//    for(int b=0; b<6; b++) {
			//        sum += vertices[cellArray[a].verts[b]].Position.Y;
			//    }
			//    cellArray[a].height = sum/6;
			//    sum = 0;
			//}
			
			vBuff = new VertexBuffer(FloralTerrainGenerator.gd,typeof(VertexMultiTex),vertices.Length,BufferUsage.WriteOnly);
			waterBuff = new VertexBuffer(FloralTerrainGenerator.gd,typeof(VertexPositionNormalTexture),6,BufferUsage.WriteOnly);

			alternateRenderTarget = new RenderTarget2D(FloralTerrainGenerator.gd,pp.BackBufferWidth,pp.BackBufferHeight,1,FloralTerrainGenerator.gd.DisplayMode.Format);
		}
		#endregion

		#region public functions
		public void setTextures(Texture2D p0,Texture2D p1,Texture2D p2,Texture2D p3) {
			if(p0 != null)
				texZero = p0;

			if(p1 != null)
				texOne = p1;

			if(p2 != null)
				texTwo = p2;

			if(p3 != null)
				texThree = p3;
		}

		public void setTextureCoords() {
			int stepper = 0;
			for(int a = 0;a < numPointsZ;a++) {
				for(int b = 0;b < numPointsX;b++) {
					setTexCoords(stepper,(float)(b%1)/1,(float)(a%1)/1);
					stepper++;
				}
			}
		}

		public void setTextureCoords(int tS) {
			texSpread = tS;
			int stepper = 0;
			for(int a = 0;a < numPointsZ;a++) {
				for(int b = 0;b < numPointsX;b++) {
					float fA = a; float fB = b;
					setTexCoords(stepper,(fB/texSpread),(fA/texSpread));
					stepper++;
				}
			}
		}

		public void moveTerrain(Vector3 mo) {
			for(int a=0; a<vertices.Length; a++) {
				vertices[a].Position += mo;
			}
		}

		public void isWireFrame(bool wf) {
			this.wf = wf;
		}

		public float getElevation(float x, float z) {
			if(x < terrainStartX || z < terrainStartZ || x > terrainStartX + width || z > terrainStartZ + depth) {
				return 0;
			} else {
				float perX,perZ;
				perX = (x - terrainStartX)/width;
				perZ = (z - terrainStartZ)/depth;

				int ptX,ptZ;
				ptX = (int) (numPointsX * perX);
				ptZ = (int) (numPointsZ * perZ);

				if(ptX >= numPointsX - 1 || ptZ >= numPointsZ - 1) {
					return 0;
				}

				Vector3 A, B, C, D, E, R, R1, n;
				//A = vertices[pointIndex[ptX+(ptZ*numPointsX)].ElementAt(0)].Position;
				//B = vertices[pointIndex[(ptX+1)+(ptZ*numPointsX)].ElementAt(0)].Position;
				//C = vertices[pointIndex[ptX+((ptZ+1)*numPointsX)].ElementAt(0)].Position;
				//D = vertices[pointIndex[(ptX+1)+((ptZ+1)*numPointsX)].ElementAt(0)].Position;
				A = vertices[ptX+(ptZ*numPointsX)].Position;
				B = vertices[(ptX+1)+(ptZ*numPointsX)].Position;
				C = vertices[ptX+((ptZ+1)*numPointsX)].Position;
				D = vertices[(ptX+1)+((ptZ+1)*numPointsX)].Position;

				R = new Vector3(x, 10000, z);
				R1 = new Vector3(x, 9000, z);

				n = Vector3.Cross(B - A, C - A);

				float d1 = Vector3.Dot(A * -1, n);
				float t = ((d1*-1) -  Vector3.Dot(R, n)) / Vector3.Dot((R1 - R), n);

				E = R + (R1 - R)*t;

				//Console.WriteLine(E.Y);
				return E.Y;
			}
		}

		/// <summary>
		/// Prepares the conwayGrid and the models
		/// </summary>
		/// <param name="density">The desired density of the plant life</param>
		public void initFlora(int density) {
			floralDensity = density;
			conwayGrid = new Boolean[density, density];
		}

		public void initFlora(Texture2D cM) {
			Color[] map = new Color[cM.Width*cM.Height];
			List<Vector2> cL = new List<Vector2>();
			cM.GetData(map);
			for(int a=0; a<cM.Width*cM.Height; a++) {
				float color = (map[a].R + map[a].B + map[a].G) / 3;
				if(color > 128) {
					cL.Add(new Vector2(a%cM.Width, (float) ((int) a/cM.Width)));
				}
			}

			initFlora(cM.Width);
			seedFlora(cL);
		}
		/// <summary>
		/// Places initial flora from which to generate the rest
		/// </summary>
		/// <param name="vL">List of Vector2s containing conwayGrid coordinates</param>
		public void seedFlora(List<Vector2> vL) {
			for(int a = 0; a<vL.Count; a++) {
				conwayGrid[(int) vL.ElementAt(a).Y,(int) vL.ElementAt(a).X] = true;
			}
		}

		public void conwayPopulate(int ticks) {
			//run conway game
			for(int a=0;a<ticks;a++) {
				cellsToBirth = new List<Vector2>();
				cellsToDie = new List<Vector2>();
				for(int y = 0; y<floralDensity; y++) {
					for(int x = 0;x<floralDensity;x++) {
						//count the number of alive among the cells eight neighbors
						int numNeighbors = 0;

						if(y != 0)
							if(conwayGrid[y-1,x])
								numNeighbors++;
						if(y != 0 && x != 0)
							if(conwayGrid[y-1,x-1])
								numNeighbors++;
						if(x != 0)
							if(conwayGrid[y,x-1])
								numNeighbors++;
						if(y != floralDensity-1 && x != 0)
							if(conwayGrid[y+1,x-1])
								numNeighbors++;
						if(y != floralDensity-1)
							if(conwayGrid[y+1,x])
								numNeighbors++;
						if(y != floralDensity-1 && x != floralDensity-1)
							if(conwayGrid[y+1,x+1])
								numNeighbors++;
						if(x != floralDensity-1)
							if(conwayGrid[y,x+1])
								numNeighbors++;
						if(y != 0 && x != floralDensity-1)
							if(conwayGrid[y-1,x+1])
								numNeighbors++;

						//apply rules
						if(conwayGrid[y,x])
							if(numNeighbors < 2 || numNeighbors > 4)
								cellsToDie.Add(new Vector2(y,x));
						if(!conwayGrid[y,x])
							if(numNeighbors == 3)
								cellsToBirth.Add(new Vector2(y,x));
					}
				}
				foreach(Vector2 cell in cellsToDie) {
					conwayGrid[(int)cell.X,(int)cell.Y] = false;
				}

				foreach(Vector2 cell in cellsToBirth) {
					conwayGrid[(int)cell.X,(int)cell.Y] = true;
				}
			}

			//convert to flora and coordinates
			for(int y = 0;y<floralDensity;y++) {
				for(int x = 0;x<floralDensity;x++) {
					if(conwayGrid[y,x]) {
						Vector3 pos = new Vector3((((float)x/floralDensity)*width) + terrainStartX,0,(((float) y/floralDensity)*depth) + terrainStartZ);
						pos += new Vector3((float)(r.NextDouble() * 40) - 20,0f,(float)(r.NextDouble() * 40) - 20);
						pos.Y = getElevation(pos.X, pos.Z);

						float scale = (float) ((r.NextDouble()*0.6) + 0.7);
						float yaw = (float)(r.NextDouble() * 360);

						Triplet<Vector2, Biome, Biome> where = getBiome(pos.Y);
						float weightedDensity = where.valueTwo.biomeDensity + ((where.valueThree.biomeDensity - where.valueTwo.biomeDensity) * where.valueOne.Y);
						if(r.NextDouble() < weightedDensity) {
							Plant p = chooseFlora(pos.Y);
							if(p.modelPath != "null") {
								p.position = pos;
								p.scale += 1-scale;
								p.yaw = yaw;
								floraMan.Add(p);
							}
						}
					}
				}
			}
		}

		public Triplet<Vector2, Biome, Biome> getBiome(float p) {
			Vector2 t = new Vector2();
			Biome b, bb;
			if(p<biome1.apexHeight) {
				t.X = (-1/biome1.apexHeight)*(p-biome1.apexHeight);
				t.Y = (1/biome1.apexHeight) * p;
				if(t.Y < 0)
					t.Y = 0;
				b = biome0; bb = biome1;
			} else if(p>=biome1.apexHeight && p<biome2.apexHeight) {
				t.X = (-1/(biome2.apexHeight-biome1.apexHeight)) * (p-biome2.apexHeight);
				t.Y = (1/(biome2.apexHeight-biome1.apexHeight)) * (p-biome1.apexHeight);
				b = biome1; bb = biome2;
			} else {
				t.X = (-1/(biome3.apexHeight-biome2.apexHeight)) * (p-biome3.apexHeight);
				if(t.X < 0)
					t.X = 0;
				t.Y = (1/(biome3.apexHeight-biome2.apexHeight)) * (p-biome2.apexHeight);
				b = biome2; bb = biome3;
			}

			float total = t.X + t.Y;
			t.X /= total;
			t.Y /= total;

			Triplet<Vector2,Biome,Biome> r = new Triplet<Vector2,Biome,Biome>();
			r.valueOne = t;
			r.valueTwo = b;
			r.valueThree = bb;
			return r;
		}

		/// <summary>
		/// returns a Plant struct with the model, density and texture list filled out
		/// </summary>
		/// <param name="m">geometric model</param>
		/// <param name="density">density within its biome</param>
		/// <returns></returns>
		public Plant makePlant(String m, float density) {
			Plant p = new Plant(m,density);
            p.model = FloralTerrainGenerator.sContent.Load<Model>(m);
			foreach(ModelMesh mesh in p.model.Meshes) {
				foreach(BasicEffect bE in mesh.Effects) {
					p.TextureList.Add(bE.Texture);
				}
			}
			return p;
		}

		public void addWater(Texture2D tex,float h,float murkiness,Vector4 loc) {
			Water w = new Water();
			w.texture = tex;
			w.loc = loc;
			w.height = h;
			w.waterMurkiness = murkiness;

			w.vertices = new VertexPositionNormalTexture[6];
			w.vertices[0].Position.X = terrainStartX + (width*loc.X);
			w.vertices[0].Position.Y = h;
			w.vertices[0].Position.Z = terrainStartZ + (depth*loc.Y);
			w.vertices[0].TextureCoordinate = Vector2.Zero;

			w.vertices[1].Position.X = terrainStartX + (width*loc.Z);
			w.vertices[1].Position.Y = h;
			w.vertices[1].Position.Z = terrainStartZ + (depth*loc.Y);
			w.vertices[0].TextureCoordinate = new Vector2(0,texSpread);

			w.vertices[2].Position.X = terrainStartX + (width*loc.X);
			w.vertices[2].Position.Y = h;
			w.vertices[2].Position.Z = terrainStartZ + (depth*loc.W);
			w.vertices[2].TextureCoordinate = new Vector2(texSpread,0);

			w.vertices[3].Position.X = terrainStartX + (width*loc.X);
			w.vertices[3].Position.Y = h;
			w.vertices[3].Position.Z = terrainStartZ + (depth*loc.W);
			w.vertices[3].TextureCoordinate = new Vector2(texSpread,0);

			w.vertices[4].Position.X = terrainStartX + (width*loc.Z);
			w.vertices[4].Position.Y = h;
			w.vertices[4].Position.Z = terrainStartZ + (depth*loc.Y);
			w.vertices[4].TextureCoordinate = new Vector2(0,texSpread);

			w.vertices[5].Position.X = terrainStartX + (width*loc.Z);
			w.vertices[5].Position.Y = h;
			w.vertices[5].Position.Z = terrainStartZ + (depth*loc.W);
			w.vertices[5].TextureCoordinate = new Vector2(texSpread);

			for(int a=0; a<w.vertices.Length; a++) {
				vertices[a].Normal = Vector3.UnitY;
			}

			waterList.Add(w);
		}
		#endregion

		#region private functions
		private void setNormals() {
			for(int a=0;a<vertices.Length;a++) {
				vertices[a].Normal = Vector3.Zero;
			}

			for(int a=0;a<triVerts.Length;a+=6) {
				VertexMultiTex vert1,vert2,vert3;
				vert1 = vertices[triVerts[a]];
				vert2 = vertices[triVerts[a+1]];
				vert3 = vertices[triVerts[a+2]];

				Vector3 vec1 = new Vector3(vert2.Position.X - vert1.Position.X,vert2.Position.Y - vert1.Position.Y,vert2.Position.Z - vert1.Position.Z);
				Vector3 vec2 = new Vector3(vert3.Position.X - vert1.Position.X,vert3.Position.Y - vert1.Position.Y,vert3.Position.Z - vert1.Position.Z);

				Vector3 norm = Vector3.Cross(-vec2,vec1);

				vertices[triVerts[a]].Normal += norm;
				vertices[triVerts[a+1]].Normal += norm;
				vertices[triVerts[a+2]].Normal += norm;

				vert1 = vertices[triVerts[a+3]];
				vert2 = vertices[triVerts[a+4]];
				vert3 = vertices[triVerts[a+5]];

				vec1 = new Vector3(vert1.Position.X - vert2.Position.X,vert1.Position.Y - vert2.Position.Y,vert1.Position.Z - vert2.Position.Z);
				vec2 = new Vector3(vert3.Position.X - vert2.Position.X,vert3.Position.Y - vert2.Position.Y,vert3.Position.Z - vert2.Position.Z);

				norm = Vector3.Cross(vec2,vec1);

				vertices[triVerts[a+3]].Normal += norm;
				vertices[triVerts[a+4]].Normal += norm;
				vertices[triVerts[a+5]].Normal += norm;
			}

			for(int a=0;a<vertices.Length;a++) {
				Vector3.Normalize(ref(vertices[a].Normal), out(vertices[a].Normal));
			}
		}

		private void setTexCoords(int pos, float tX, float tY) {
			//foreach (int a in pointIndex[pos]) {
			vertices[pos].TextureCoordinate.X = tX;
			vertices[pos].TextureCoordinate.Y = tY;
			//}
		}

		private void setTextureWeight(int stepper,float p) {
            //foreach (int stepper in pointIndex[pos]){
                vertices[stepper].TextureWeight = Vector4.Zero;
				if(p<biome1.apexHeight) {
					vertices[stepper].TextureWeight.X = (-1/biome1.apexHeight)*(p-biome1.apexHeight);
					vertices[stepper].TextureWeight.Y = (1/biome1.apexHeight) * p;
					if(vertices[stepper].TextureWeight.Y < 0)
						vertices[stepper].TextureWeight.Y = 0;

					float total = vertices[stepper].TextureWeight.X + vertices[stepper].TextureWeight.Y;
					vertices[stepper].TextureWeight.X /= total;
					vertices[stepper].TextureWeight.Y /= total;
				} else if(p>=biome1.apexHeight && p<biome2.apexHeight) {
					vertices[stepper].TextureWeight.Y = (-1/(biome2.apexHeight-biome1.apexHeight)) * (p-biome2.apexHeight);
					vertices[stepper].TextureWeight.Z = (1/(biome2.apexHeight-biome1.apexHeight)) * (p-biome1.apexHeight);

					vertices[stepper].TextureWeight.Y = vertices[stepper].TextureWeight.Y/(vertices[stepper].TextureWeight.Y + vertices[stepper].TextureWeight.Z);
					vertices[stepper].TextureWeight.Z = vertices[stepper].TextureWeight.Z/(vertices[stepper].TextureWeight.Y + vertices[stepper].TextureWeight.Z);
				} else if(p>=biome2.apexHeight) {
					vertices[stepper].TextureWeight.Z = (-1/(biome3.apexHeight-biome2.apexHeight)) * (p-biome3.apexHeight);
					if(vertices[stepper].TextureWeight.Z < 0)
						vertices[stepper].TextureWeight.Z = 0;
					vertices[stepper].TextureWeight.W = (1/(biome3.apexHeight-biome2.apexHeight)) * (p-biome2.apexHeight);

					float total = vertices[stepper].TextureWeight.Z + vertices[stepper].TextureWeight.W;
					vertices[stepper].TextureWeight.Z /= total;
					vertices[stepper].TextureWeight.W /= total;

					if(vertices[stepper].TextureWeight.Z + vertices[stepper].TextureWeight.W > 1.0) {
						//Console.Write(vertices[stepper].TextureWeight.Z+" ");
						//Console.WriteLine(vertices[stepper].TextureWeight.W);
					}
				}
				//vertices[stepper].TextureWeight.Normalize();
				/*for(int a=0;a<biomeList.Count;a++) {
					if(a == 0) {
						if(p<biomeList.ElementAt(a+1).apexHeight) {
							vertices[stepper].TextureWeight.X = (-1/biomeList.ElementAt(a+1).apexHeight)*(p-biomeList.ElementAt(a+1).apexHeight);
							vertices[stepper].TextureWeight.Y = (1/biomeList.ElementAt(a+1).apexHeight) * p;
						}
					} else if(a == biomeList.Count - 1) {
						if(p>=biomeList.ElementAt(a).apexHeight) {
							vertices[stepper].TextureWeight.Z = (-1/(heighest-biomeList.ElementAt(a).apexHeight)) * (p-heighest);
							vertices[stepper].TextureWeight.W = (1/(heighest-biomeList.ElementAt(a).apexHeight)) * (p-biomeList.ElementAt(a).apexHeight);
						}
					} else {
						if(p>=biomeList.ElementAt(a) && p<biomeList.ElementAt(a+1)) {
							vertices[stepper].TextureWeight.Y = (-1/(biomeList.ElementAt(a+1)-biomeList.ElementAt(a))) * (p-biomeList.ElementAt(a+1));
							vertices[stepper].TextureWeight.Z = (1/(biomeList.ElementAt(a+1)-biomeList.ElementAt(a))) * (p-biomeList.ElementAt(a));
						}
					}
				}*/
            //}
		}

		private float[,] getColorData(Texture2D image) {
			Color[] colors = new Color[image.Height * image.Width];
			image.GetData(colors);
			Color[,] colors2D = new Color[image.Height, image.Width];

			int stepper = 0;
			for(int a=0;a<image.Height;a++) {
				for(int b=0;b<image.Width;b++) {
					colors2D[a, b] = colors[stepper];
					stepper++;
				}
			}

			float[,] bwData = new float[image.Height, image.Width];
			for(int a=0;a<image.Height;a++) {
				for(int b=0;b<image.Width;b++) {
					bwData[a, b] = (colors2D[a, b].R + colors2D[a, b].G + colors2D[a, b].B)/3;
				}
			}

			return bwData;

		}

		//private void positionPointY(int pos, float pY) {
		//    foreach(int a in pointIndex[pos]) {
		//        vertices[pos].Position.Y = pY;
		//    }
		//}

		private void createGrid(int pCellsWide, int pCellsDeep) {
			totalCells = cellsDeep * cellsWide;
			totalFaces = totalCells * 2;
			//totalNumVertices = totalFaces * 3;

			cellWidth = width/cellsWide;
			cellDepth = depth/cellsDeep;

			numPointsX = cellsWide + 1;
			numPointsZ = cellsDeep + 1;
			totalNumPoints = numPointsX * numPointsZ;

			terrainStartX = -width/2;
			terrainStartZ = -depth/2;

			vertices = new VertexMultiTex[totalNumPoints];
			//pointIndex = new List<int>[totalNumPoints];
			//for(int a=0; a<pointIndex.Length; a++) {
			//    pointIndex[a] = new List<int>();
			//}

			triVerts = new int[totalFaces*3];
			int counter = 0;
			for(int a=0;a<numPointsZ;a++) {
				for(int b=0;b<numPointsX;b++) {
					//vertices[counter] = new VertexMultiTex();
					vertices[counter].Position = new Vector3(terrainStartX + cellWidth*b,0,terrainStartZ + cellDepth*a);
					//vertices[counter].Normal = Vector3.Zero;
					counter++;
				}
			}

			int startVertex = 0;
			int index = 0;
			for(int i=0;i<cellsDeep;i++) {
				for(int j=0;j<cellsWide;j++) {
					triVerts[index] = startVertex;
					triVerts[index+1] = startVertex + 1;
					triVerts[index+2] = startVertex + numPointsX;

					triVerts[index+3] = startVertex+1;
					triVerts[index+4] = startVertex + numPointsX + 1;
					triVerts[index+5] = startVertex + numPointsX;

					index += 6;
					startVertex++;
				}
				startVertex++;
			}

			//position vertices by triangle
			//int stepper = 0;
			//for(int a=0; a<cellsDeep; a++) {
			//    for(int b=0; b<cellsWide; b++) {
			//        vertices[stepper].Position = new Vector3(terrainStartX + cellWidth*b, lowest, terrainStartZ + cellDepth*a);
			//        pointIndex[(a*numPointsX)+b].Add(stepper);
			//        vertices[stepper+1].Position = new Vector3(terrainStartX + cellWidth*(b+1), lowest, terrainStartZ + cellDepth*a);
			//        pointIndex[(a*numPointsX)+b+1].Add(stepper+1);
			//        vertices[stepper+2].Position = new Vector3(terrainStartX + cellWidth*b, lowest, terrainStartZ + cellDepth*(a+1));
			//        pointIndex[((a+1)*numPointsX)+b].Add(stepper+2);

			//        vertices[stepper+3].Position = new Vector3(terrainStartX + cellWidth*(b+1), lowest, terrainStartZ + cellDepth*a);
			//        pointIndex[(a*numPointsX)+b+1].Add(stepper+3);
			//        vertices[stepper+4].Position = new Vector3(terrainStartX + cellWidth*(b+1), lowest, terrainStartZ + cellDepth*(a+1));
			//        pointIndex[((a+1)*numPointsX)+b+1].Add(stepper+4);
			//        vertices[stepper+5].Position = new Vector3(terrainStartX + cellWidth*b, lowest, terrainStartZ + cellDepth*(a+1));
			//        pointIndex[((a+1)*numPointsX)+b].Add(stepper+5);

			//        cellArray[(cellsWide*a)+b] = new Cell();
			//        cellArray[(cellsWide*a)+b].verts = new int[6] {stepper, stepper+1, stepper+2, stepper+3, stepper+4, stepper+5};

			//        stepper += 6;
			//    }
			//}
		}

		private Plant chooseFlora(float pY) {
			float threshold;

			if(pY<biome1.apexHeight) {
				threshold = (1/biome1.apexHeight) * pY;
				if(r.NextDouble() < threshold)
					return getRandomBiomePlant(biome1);
				else
					return getRandomBiomePlant(biome0);
			} else if(pY>=biome1.apexHeight && pY<biome2.apexHeight) {
				threshold = (1/(biome2.apexHeight-biome1.apexHeight)) * (pY-biome1.apexHeight);
				if(r.NextDouble() < threshold)
					return getRandomBiomePlant(biome2);
				else
					return getRandomBiomePlant(biome1);
			} else { //above biome2
				threshold = (1/(heighest-biome2.apexHeight)) * (pY-biome2.apexHeight);
				if(r.NextDouble() < threshold)
					return getRandomBiomePlant(biome3);
				else
					return getRandomBiomePlant(biome2);
			}
		}

		private Plant getRandomBiomePlant(Biome b) {
			float totalDensity = 0;
			foreach(Plant p in b.floraList) {
				totalDensity += p.density;
			}
			float chooser = (float) (r.NextDouble() * totalDensity);
			float counter = 0;
			foreach(Plant p in b.floraList) {
				if(chooser <= counter + p.density) {
					return p;
				}
				counter += p.density;
			}

			return new Plant("null",0);
		}
		#endregion
		
		#region update and draw

		public void Update(GameTime gt) {
			effect.Parameters["View"].SetValue(FloralTerrainGenerator.viewMatrix);

			if(wf) {
				FloralTerrainGenerator.gd.RenderState.FillMode = FillMode.WireFrame;
			} else {
				FloralTerrainGenerator.gd.RenderState.FillMode = FillMode.Solid;
			}
		}

		public void drawTerrain(SpriteBatch spriteBatch) {
			FloralTerrainGenerator.gd.RenderState.CullMode = CullMode.CullCounterClockwiseFace;
            FloralTerrainGenerator.gd.VertexDeclaration = new VertexDeclaration(FloralTerrainGenerator.gd, vE);
			vBuff.SetData(vertices);
			FloralTerrainGenerator.gd.Vertices[0].SetSource(vBuff,0,sizeof(float) * 12);

			effect.CurrentTechnique = effect.Techniques["MultiTextured"];
            effect.Parameters["Texture0"].SetValue(texZero);
            effect.Parameters["Texture1"].SetValue(texOne);
            effect.Parameters["Texture2"].SetValue(texTwo);
            effect.Parameters["Texture3"].SetValue(texThree);

			effect.Parameters["lightPos"].SetValue(lightPos);
			effect.Parameters["lightPower"].SetValue(lightPower);
			effect.Parameters["ambientLight"].SetValue(ambientLight);

			effect.Parameters["World"].SetValue(Matrix.Identity);

			effect.Begin();
			foreach(EffectPass pass in effect.CurrentTechnique.Passes) {
				pass.Begin();

				FloralTerrainGenerator.gd.DrawUserIndexedPrimitives<VertexMultiTex>(PrimitiveType.TriangleList,vertices,0,vertices.Length,triVerts,0,triVerts.Length/3);
				//FloralTerrainGenerator.gd.DrawPrimitives(PrimitiveType.TriangleList, 0, totalFaces);
				pass.End();
			}
			effect.End();
		}

		public void drawWater(SpriteBatch spriteBatch) {
			//water rendering
			FloralTerrainGenerator.gd.RenderState.CullMode = CullMode.CullCounterClockwiseFace;
			FloralTerrainGenerator.gd.VertexDeclaration = new VertexDeclaration(FloralTerrainGenerator.gd,VertexPositionNormalTexture.VertexElements);

			foreach(Water w in waterList) {

				/*//make clipping plane
				Plane clipPlane;
				Vector4 planeVec = new Vector4(new Vector3(0, 1, 0),-w.height);
				//planeVec *= -1;
				float ar = (float) (GraphicsDeviceManager.DefaultBackBufferWidth/GraphicsDeviceManager.DefaultBackBufferHeight);
				Matrix worldView = FloralTerrainGenerator.viewMatrix * Matrix.CreatePerspectiveFieldOfView(MathHelper.PiOver4,ar,1,1000);
				Matrix InvWorldViewProj = Matrix.Invert(worldView);
				InvWorldViewProj = Matrix.Transpose(InvWorldViewProj);
				planeVec = Vector4.Transform(planeVec,InvWorldViewProj);
				clipPlane = new Plane(planeVec);
				
				////render refraction
				FloralTerrainGenerator.gd.ClipPlanes[0].Plane = clipPlane;
				FloralTerrainGenerator.gd.ClipPlanes[0].IsEnabled = true;
				FloralTerrainGenerator.gd.SetRenderTarget(0,alternateRenderTarget);
				FloralTerrainGenerator.gd.Clear(ClearOptions.Target | ClearOptions.DepthBuffer,Color.Black,1f,0);
				drawTerrain(spriteBatch);
				drawPlants(spriteBatch);
				FloralTerrainGenerator.gd.ClipPlanes[0].IsEnabled = false;
				FloralTerrainGenerator.gd.SetRenderTarget(0,null);
				Texture2D refractionTexture = alternateRenderTarget.GetTexture();
				//refractionTexture.Save("refractionmap.jpg",ImageFileFormat.Jpg);

				//reflect camera for reflection rendering
				Matrix oldView = FloralTerrainGenerator.viewMatrix;
				Vector3 refPos = FloralTerrainGenerator.cPos;
				refPos.Y = -FloralTerrainGenerator.cPos.Y;
				Matrix refM = Matrix.Identity;
				refM *= Matrix.CreateTranslation(refPos); ;
				refM *= Matrix.CreateRotationY(FloralTerrainGenerator.crY);
				refM *= Matrix.CreateRotationX(-FloralTerrainGenerator.crX);
				refM *= Matrix.CreateRotationZ(-FloralTerrainGenerator.crZ);

				FloralTerrainGenerator.setViewMatrix(refM);

				//make clipping plane
				planeVec = new Vector4(new Vector3(0,1,0),-w.height);
				planeVec *= -1;
				worldView = FloralTerrainGenerator.viewMatrix * Matrix.CreatePerspectiveFieldOfView(MathHelper.PiOver4,ar,1,1000);
				InvWorldViewProj = Matrix.Invert(worldView);
				InvWorldViewProj = Matrix.Transpose(InvWorldViewProj);
				planeVec = Vector4.Transform(planeVec,InvWorldViewProj);
				clipPlane = new Plane(planeVec);

				////render reflection
				FloralTerrainGenerator.gd.ClipPlanes[0].Plane = clipPlane;
				FloralTerrainGenerator.gd.ClipPlanes[0].IsEnabled = true;
				FloralTerrainGenerator.gd.SetRenderTarget(0,alternateRenderTarget);
				FloralTerrainGenerator.gd.Clear(ClearOptions.Target | ClearOptions.DepthBuffer,Color.Black,1f,0);
				drawTerrain(spriteBatch);
				drawPlants(spriteBatch);
				FloralTerrainGenerator.gd.ClipPlanes[0].IsEnabled = false;
				FloralTerrainGenerator.gd.SetRenderTarget(0,null);
				Texture2D reflectionTexture = alternateRenderTarget.GetTexture();
				reflectionTexture.Save("refractionmap.jpg",ImageFileFormat.Jpg);

				FloralTerrainGenerator.setViewMatrix(oldView);
                */
				waterBuff.SetData(w.vertices);
				FloralTerrainGenerator.gd.Vertices[0].SetSource(waterBuff,0,sizeof(float) * 8);

				effect.CurrentTechnique = effect.Techniques["WaterShader"];
				effect.Parameters["Texture0"].SetValue(w.texture);

                effect.Parameters["lightPos"].SetValue(lightPos);
                effect.Parameters["lightPower"].SetValue(lightPower);
                effect.Parameters["ambientLight"].SetValue(ambientLight);
				effect.Parameters["wMurk"].SetValue(w.waterMurkiness);

				effect.Parameters["World"].SetValue(Matrix.Identity);

				effect.Begin();
				foreach(EffectPass pass in effect.CurrentTechnique.Passes) {
					pass.Begin();

					FloralTerrainGenerator.gd.DrawPrimitives(PrimitiveType.TriangleList,0,6);
					pass.End();
				}
				effect.End();
			}
		}

		public void drawPlants(SpriteBatch spriteBatch) {
			FloralTerrainGenerator.gd.RenderState.CullMode = CullMode.None;

			FloralTerrainGenerator.gd.RenderState.AlphaTestEnable = true;
			FloralTerrainGenerator.gd.RenderState.AlphaFunction = CompareFunction.GreaterEqual;
			FloralTerrainGenerator.gd.RenderState.ReferenceAlpha = 200;

			foreach(Plant p in floraMan) {
				Matrix[] tMBT = new Matrix[p.model.Bones.Count];
				p.model.CopyAbsoluteBoneTransformsTo(tMBT);

				int texCounter = 0;
				foreach(ModelMesh mesh in p.model.Meshes) {
					foreach(ModelMeshPart meshPart in mesh.MeshParts) {
						meshPart.Effect = effect;//.Clone(FloralTerrainGenerator.gd);
					}

					foreach(Effect mEffect in mesh.Effects) {
						mEffect.Parameters["lightPos"].SetValue(lightPos);
						mEffect.Parameters["lightPower"].SetValue(lightPower);
						mEffect.Parameters["ambientLight"].SetValue(ambientLight);

						mEffect.Parameters["World"].SetValue(tMBT[mesh.ParentBone.Index] * Matrix.CreateScale(p.scale) * Matrix.CreateRotationY(MathHelper.ToRadians(p.yaw)) * Matrix.CreateTranslation(p.position.X,p.position.Y,p.position.Z));
						mEffect.CurrentTechnique = mEffect.Techniques["SingleAlphaTexture"];
						mEffect.Parameters["Texture0"].SetValue(p.TextureList.ElementAt(texCounter));
					}
					texCounter++;

					mesh.Draw();
				}
			}

			FloralTerrainGenerator.gd.RenderState.DepthBufferWriteEnable = false;
			FloralTerrainGenerator.gd.RenderState.AlphaBlendEnable = true;
			FloralTerrainGenerator.gd.RenderState.SourceBlend = Blend.SourceAlpha;
			FloralTerrainGenerator.gd.RenderState.DestinationBlend = Blend.InverseSourceAlpha;
			FloralTerrainGenerator.gd.RenderState.AlphaFunction = CompareFunction.Less;
			FloralTerrainGenerator.gd.RenderState.ReferenceAlpha = 200;

			foreach(Plant p in floraMan) {
				Matrix[] tMBT = new Matrix[p.model.Bones.Count];
				p.model.CopyAbsoluteBoneTransformsTo(tMBT);

				int texCounter = 0;
				foreach(ModelMesh mesh in p.model.Meshes) {
					foreach(ModelMeshPart meshPart in mesh.MeshParts) {
						meshPart.Effect = effect;//.Clone(FloralTerrainGenerator.gd);
					}

					foreach(Effect mEffect in mesh.Effects) {
						mEffect.Parameters["lightPos"].SetValue(lightPos);
						mEffect.Parameters["lightPower"].SetValue(lightPower);
						mEffect.Parameters["ambientLight"].SetValue(ambientLight);

						mEffect.Parameters["World"].SetValue(tMBT[mesh.ParentBone.Index] * Matrix.CreateScale(p.scale) * Matrix.CreateRotationY(MathHelper.ToRadians(p.yaw)) * Matrix.CreateTranslation(p.position.X,p.position.Y,p.position.Z));
						mEffect.CurrentTechnique = mEffect.Techniques["SingleAlphaTexture"];
						mEffect.Parameters["Texture0"].SetValue(p.TextureList.ElementAt(texCounter));
					}
					texCounter++;

					mesh.Draw();
				}
			}

			FloralTerrainGenerator.gd.RenderState.DepthBufferWriteEnable = true;
			FloralTerrainGenerator.gd.RenderState.AlphaTestEnable = false;
			FloralTerrainGenerator.gd.RenderState.AlphaBlendEnable = false;
		}
		#endregion
	}
}

#region structs
struct Triplet<A,B,C> {
	public A valueOne;
	public B valueTwo;
	public C valueThree;
}

struct Water {
	public Texture2D texture;
	public Texture2D refractionTexture;
	public Texture2D reflectionTexture;
	public float height;
	public VertexPositionNormalTexture[] vertices;
	/// <summary>
	/// coordinates of upper-left and lower-right, in percentage of total terrain area (0 - 1.0)
	/// </summary>
	public Vector4 loc;
	public float waterMurkiness;
}

struct Biome {
	/// <summary>
	/// Height which is %100 this biome
	/// </summary>
	public float apexHeight;
	public List<Plant> floraList;
	public Texture2D texture;
	public float biomeDensity;

	public Biome(float aH) {
		apexHeight = aH;
		floraList = new List<Plant>();
		texture = null;
		biomeDensity = 0;
	}
}

struct Cell {
	public float height;
	public int[] verts;
}

struct Plant {
	public Vector3 position;
	public float yaw;
	public float scale;
    public String modelPath;
	public Model model;
	public List<Texture2D> TextureList;
	/// <summary>
	/// density of this plant species
	/// </summary>
	public float density;
	Boolean isPatch;
	Boolean orient;

	public Plant(String pModel, float pDens) {
		position = Vector3.Zero;
		yaw = 0;
		scale = 1;
		modelPath = pModel;
		density = pDens;
		TextureList = new List<Texture2D>();
		isPatch = false;
		orient = false;
        model = null;
	}

    public String toString() {
        return modelPath + " " + position.ToString() + " " + yaw.ToString() + " " + scale.ToString() + " " + isPatch.ToString() + " " + orient.ToString();
    }
}

struct VertexMultiTex {
	public Vector3 Position;
	public Vector2 TextureCoordinate;
	public Vector3 Normal;
	public Vector4 TextureWeight;

	public VertexMultiTex(Vector3 _Pos) {
		Position = _Pos;
		TextureCoordinate = Vector2.Zero;
		TextureWeight = Vector4.Zero;
		Normal = Vector3.One;
	}

    public String toString() {
        return Position.ToString() + " " + TextureCoordinate.ToString() + " " + Normal.ToString() + " " + TextureWeight.ToString();
    }
}
#endregion